package view_server

import (
	"bigdevops/src/common"
	"bigdevops/src/config"
	"bigdevops/src/models"
	"fmt"
	"github.com/gin-gonic/gin"
	"github.com/go-playground/validator/v10"
	"go.uber.org/zap"
	"strconv"
	"time"
)

func createMonitorOndutyGroup(c *gin.Context) {

	sc := c.MustGet(common.GIN_CTX_CONFIG_CONFIG).(*config.ServerConfig)
	var reqObj models.MonitorOndutyGroup
	err := c.ShouldBindJSON(&reqObj)
	if err != nil {
		sc.Logger.Error("解析新增值班组请求失败", zap.Any("值班组", reqObj), zap.Error(err))
		common.FailWithMessage(err.Error(), c)
		return
	}

	// 在这里校验字段，是否必填，范围是否正确
	err = validate.Struct(reqObj)
	if err != nil {

		// 这里为什么要判断错误是否是 ValidationErrors
		if errors, ok := err.(validator.ValidationErrors); ok {
			common.ReqBadFailWithWithDetailed(

				gin.H{
					"翻译前": err.Error(),
					"翻译后": errors.Translate(trans),
				},
				"请求出错",
				c,
			)
			return
		}
		common.ReqBadFailWithMessage(err.Error(), c)
		return

	}

	userName := c.MustGet(common.GIN_CTX_JWT_USER_NAME).(string)
	dbUser, err := models.GetUserByUserName(userName)
	if err != nil {
		sc.Logger.Error("通过token解析到的userName去数据库中找User失败",
			zap.Error(err),
		)
		common.ReqBadFailWithMessage(fmt.Sprintf("通过token解析到的userName去数据库中找User失败:%v", err.Error()), c)
		return
	}

	reqObj.UserID = dbUser.ID

	reqObj.Members = commonGetUsersByNames(reqObj.UserNames, sc.Logger, c)

	// 下面级联创建
	err = reqObj.CreateOne()
	if err != nil {
		sc.Logger.Error("新增值班组数据库失败", zap.Any("值班组", reqObj), zap.Error(err))
		common.FailWithMessage(err.Error(), c)
		return
	}

	common.OkWithMessage("创建成功", c)

}

func createMonitorOndutyGroupChange(c *gin.Context) {

	sc := c.MustGet(common.GIN_CTX_CONFIG_CONFIG).(*config.ServerConfig)
	var reqObj models.MonitorOndutyChange
	err := c.ShouldBindJSON(&reqObj)
	if err != nil {
		sc.Logger.Error("解析新增换班请求失败", zap.Any("值班组", reqObj), zap.Error(err))
		common.FailWithMessage(err.Error(), c)
		return
	}

	// 在这里校验字段，是否必填，范围是否正确
	err = validate.Struct(reqObj)
	if err != nil {

		// 这里为什么要判断错误是否是 ValidationErrors
		if errors, ok := err.(validator.ValidationErrors); ok {
			common.ReqBadFailWithWithDetailed(

				gin.H{
					"翻译前": err.Error(),
					"翻译后": errors.Translate(trans),
				},
				"请求出错",
				c,
			)
			return
		}
		common.ReqBadFailWithMessage(err.Error(), c)
		return

	}

	userName := c.MustGet(common.GIN_CTX_JWT_USER_NAME).(string)
	dbUser, err := models.GetUserByUserName(userName)
	if err != nil {
		sc.Logger.Error("通过token解析到的userName去数据库中找User失败",
			zap.Error(err),
		)
		common.ReqBadFailWithMessage(fmt.Sprintf("通过token解析到的userName去数据库中找User失败:%v", err.Error()), c)
		return
	}

	_, err = models.GetMonitorOndutyGroupById(int(reqObj.OndutyGroupId))
	if err != nil {
		sc.Logger.Error("根据id找值班组错误", zap.Any("值班组", reqObj), zap.Error(err))
		common.FailWithMessage(err.Error(), c)
		return
	}

	// 找人
	dbOriginUser, err := models.GetUserByUserName(reqObj.OriginUserName)
	if err != nil {
		sc.Logger.Error("通过OriginUserName去数据库中找User失败",
			zap.Error(err),
		)
		common.ReqBadFailWithMessage(fmt.Sprintf("通过OriginUserName去数据库中找User失败:%v", err.Error()), c)
		return
	}

	dbTargetUser, err := models.GetUserByUserName(reqObj.TargetUserName)
	if err != nil {
		sc.Logger.Error("通过TargetUserName去数据库中找User失败",
			zap.Error(err),
		)
		common.ReqBadFailWithMessage(fmt.Sprintf("通过TargetUserName去数据库中找User失败:%v", err.Error()), c)
		return
	}

	reqObj.UserID = dbUser.ID
	reqObj.OnDutyUserId = dbTargetUser.ID
	reqObj.OriginUserId = dbOriginUser.ID

	// 下面级联创建
	err = reqObj.CreateOne()
	if err != nil {
		sc.Logger.Error("新增换班请求数据库失败", zap.Any("值班组", reqObj), zap.Error(err))
		common.FailWithMessage(err.Error(), c)
		return
	}

	common.OkWithMessage("创建成功", c)

}

func updateMonitorOndutyGroup(c *gin.Context) {
	sc := c.MustGet(common.GIN_CTX_CONFIG_CONFIG).(*config.ServerConfig)
	var reqObj models.MonitorOndutyGroup
	err := c.ShouldBindJSON(&reqObj)
	if err != nil {
		sc.Logger.Error("解析新增值班组请求失败", zap.Any("值班组", reqObj), zap.Error(err))
		common.FailWithMessage(err.Error(), c)
		return
	}

	// 在这里校验字段，是否必填，范围是否正确
	err = validate.Struct(reqObj)
	if err != nil {

		// 这里为什么要判断错误是否是 ValidationErrors
		if errors, ok := err.(validator.ValidationErrors); ok {
			common.ReqBadFailWithWithDetailed(

				gin.H{
					"翻译前": err.Error(),
					"翻译后": errors.Translate(trans),
				},
				"请求出错",
				c,
			)
			return
		}
		common.ReqBadFailWithMessage(err.Error(), c)
		return

	}

	_, err = models.GetMonitorOndutyGroupById(int(reqObj.ID))
	if err != nil {
		sc.Logger.Error("根据id找值班组错误", zap.Any("值班组", reqObj), zap.Error(err))
		common.FailWithMessage(err.Error(), c)
		return
	}

	reqObj.Members = commonGetUsersByNames(reqObj.UserNames, sc.Logger, c)

	err = reqObj.UpdateMembers()

	if err != nil {
		sc.Logger.Error("更新值班组关联members错误", zap.Any("值班组", reqObj), zap.Error(err))
		common.FailWithMessage(err.Error(), c)
		return
	}

	err = reqObj.UpdateOne()

	if err != nil {
		sc.Logger.Error("更新值班组本身错误", zap.Any("值班组", reqObj), zap.Error(err))
		common.FailWithMessage(err.Error(), c)
		return
	}

	common.OkWithMessage("更新成功", c)
}

func getMonitorOndutyGroupList(c *gin.Context) {

	sc := c.MustGet(common.GIN_CTX_CONFIG_CONFIG).(*config.ServerConfig)

	currentPage, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
	pageSize, _ := strconv.Atoi(c.DefaultQuery("pageSize", "100"))

	offset := 0
	limit := 0
	limit = pageSize
	if currentPage > 1 {
		offset = (currentPage - 1) * limit
	}
	// 这里只能新增 不能去掉，因为是 多种资源共用的query
	searchUserID := c.DefaultQuery("UserID", "")
	searchUserIDInt, _ := strconv.Atoi(searchUserID)
	searchName := c.DefaultQuery("name", "")

	// 	数据库中拿到所有的menu列表

	objs, err := models.GetMonitorOndutyGroupAll()
	if err != nil {
		sc.Logger.Error("去数据库中拿所有的值班组错误",
			zap.Error(err),
		)
		common.ReqBadFailWithMessage(fmt.Sprintf("去数据库中拿所有的值班组错误:%v", err.Error()), c)
		return
	}

	allIds := []int{}
	//// 遍历 role 准备menuIds 列表
	for _, obj := range objs {
		obj := obj

		// 在这里处理查询
		if searchName != "" && obj.Name != searchName {
			continue
		}

		// 在这里处理查询
		if searchUserID != "" && int(obj.UserID) != searchUserIDInt {
			continue
		}
		allIds = append(allIds, int(obj.ID))

		//role.MenuIds = menuIds
	}

	if len(allIds) == 0 {
		common.OkWithDetailed(allIds, "ok", c)
		return
	}
	objs, err = models.GetMonitorOndutyGroupByIdsWithLimitOffset(allIds, limit, offset)
	if err != nil {
		sc.Logger.Error("limit-offset去数据库中拿所有的任务错误",
			zap.Error(err),
		)
		common.ReqBadFailWithMessage(fmt.Sprintf("limit-offset去数据库中拿所有的脚本模板错误:%v", err.Error()), c)
		return
	}
	for _, obj := range objs {
		obj.FillFrontAllData()
		obj.FillTodayOnDutyUser()

	}

	resp := &ResponseResourceCommon{
		//Total: models.GetMonitorOndutyGroupCount(), 因为是带了查询条件的 所以 count 不能是all 而是 过滤后的total

		Total: len(allIds),
		Items: objs,
	}

	common.OkWithDetailed(resp, "ok", c)
}

type OndutyPlanResponse struct {
	Details       []OndutyOne       `json:"details"`
	Map           map[string]string `json:"map"`
	UserNameMap   map[string]string `json:"userNameMap"`
	OriginUserMap map[string]string `json:"originUserMap"`
}

type OndutyOne struct {
	Date       string       `json:"date"`
	User       *models.User `json:"user"`
	OriginUser string       `json:"originUser"`
}

// 给一个 时间范围 ，返回给你 这个值班组的值班计划

// 时间范围 分几种情况
// 1 start < end < today 完全历史 当前时间 说明应该从历史记录中返回
// 2 start < today < end 一部分是历史 一部分是未来
// 3 today < start < end 完全是未来
func getMonitorOndutyGroupOneFuturePlan(c *gin.Context) {
	sc := c.MustGet(common.GIN_CTX_CONFIG_CONFIG).(*config.ServerConfig)
	// 校验一下 menu字段
	id := c.Param("id")

	// 查询的起止时间： 开始 可能是之前的天数 30天前 到今天
	// 结束也可能是 未来的时间 30天后
	startDay := c.DefaultQuery("startDay", "")
	endDay := c.DefaultQuery("endDay", "")
	var (
		startDayTime, endDayTime time.Time
		err                      error
	)
	startDayTime, err = time.Parse("2006-01-02", startDay)
	if err != nil {
		sc.Logger.Error("解析日期错误", zap.Any("值班组", id), zap.Error(err))
		common.FailWithMessage(err.Error(), c)
		return
	}
	endDayTime, err = time.Parse("2006-01-02", endDay)
	if err != nil {
		sc.Logger.Error("解析日期错误", zap.Any("值班组", id), zap.Error(err))
		common.FailWithMessage(err.Error(), c)
		return
	}
	if endDayTime.Sub(startDayTime) < 0 {
		msg := "endDayTime比startDayTime小"
		sc.Logger.Error(msg, zap.Any("值班组", id), zap.Error(err))
		common.FailWithMessage(msg, c)
		return
	}

	// 先 去db中根据id找到这个user
	intVar, _ := strconv.Atoi(id)
	dbObj, err := models.GetMonitorOndutyGroupById(intVar)
	if err != nil {
		sc.Logger.Error("根据id找值班组错误", zap.Any("任务实例", id), zap.Error(err))
		common.FailWithMessage(err.Error(), c)
		return
	}
	dbObj.FillFrontAllData()
	if dbObj.Members == nil {
		sc.Logger.Error("根据id找值班组 member 为空错误", zap.Any("任务实例", id), zap.Error(err))
		common.FailWithMessage(err.Error(), c)
		return
	}

	// 需要判断 是否是 完全历史
	todayTime := time.Now()
	if todayTime.Sub(endDayTime) > 0 {
		historys, err := models.GetMonitorOndutyHistoryByOnDutyGroupIdAndTimeRange(intVar, startDay, endDay)
		if err != nil {
			sc.Logger.Error("根据值班组id找历史错误", zap.Any("值班组id", id), zap.Error(err))
			common.FailWithMessage(err.Error(), c)
			return
		}

		tmpRes := []OndutyOne{}

		ondutyPlanResponse := OndutyPlanResponse{}
		tmp := map[string]string{}
		tmpUserName := map[string]string{}
		originUserMap := map[string]string{}
		for _, history := range historys {
			history := history
			user, err := models.GetUserById(int(history.OnDutyUserId))
			if err != nil {
				continue
			}
			one := OndutyOne{
				Date: history.DateString,
				User: user,
			}
			if history.OriginUserId > 0 {
				userOrigin, _ := models.GetUserById(int(history.OriginUserId))
				if userOrigin.RealName != "" {
					one.OriginUser = userOrigin.RealName
				}
			}

			tmpRes = append(tmpRes,
				one,
			)
			tmp[history.DateString] = one.User.RealName
			tmpUserName[history.DateString] = one.User.Username
			originUserMap[history.DateString] = one.OriginUser

		}
		ondutyPlanResponse.Details = tmpRes
		ondutyPlanResponse.Map = tmp
		ondutyPlanResponse.UserNameMap = tmpUserName
		ondutyPlanResponse.OriginUserMap = originUserMap
		common.OkWithData(ondutyPlanResponse, c)
		return

	}

	//  后面的逻辑可以混在一起 ：从start 到今天

	// 值班组的创建时间
	// 给定的时间返回
	// 最简模式返回 值班计划
	//createDate := dbObj.CreatedAt.Date()
	// 查询开始时间 应该比 值班组创建时间要晚
	// 比如 1.1 创建的  查询 1.10 - 1.20 的值班

	// 查询时间返回 分2 部分： 1部分是  start 到  today 历史的：查历史表
	// 查询时间返回 分2 部分： 1部分是  today 到  end 未来的  ：按顺序算出
	// 先查询历史 ：谁给历史表不断更新，写入：ticker
	//today := time.Now().Format("2006-01-02")
	// 判断 startDay 和today的关系
	tmpRes := []OndutyOne{}
	todayDate := time.Now().Format("2006-01-02")

	historys, err := models.GetMonitorOndutyHistoryByOnDutyGroupIdAndTimeRange(intVar, startDay, todayDate)
	if err != nil {
		sc.Logger.Error("根据值班组id找历史错误", zap.Any("值班组id", id), zap.Error(err))
		common.FailWithMessage(err.Error(), c)
		return
	}
	for _, history := range historys {
		history := history
		user, err := models.GetUserById(int(history.OnDutyUserId))
		if err != nil {
			continue
		}
		tmpRes = append(tmpRes,
			OndutyOne{
				Date: history.DateString,
				User: user,
			},
		)
	}

	// 再生成 未来的计划
	//yestoday := common.GetDayAgoDate(-1)
	// 昨天的值班人
	//yestodayHistory, _ := models.GetMonitorOndutyHistoryByOndutyGroupIdAndDay(uint(intVar), yestoday)
	todayHistory, _ := models.GetMonitorOndutyHistoryByOndutyGroupIdAndDay(uint(intVar), todayDate)
	// [ "admin", "test", "auto_order_robot", "mock-user-0", "mock-user-1", "mock-user-2", "mock-user-3", "mock-user-4" ]

	// 长度是8
	// 今天是 auto_order_robot index=2 第 3个
	// 只需要再生成 6天

	onDutyUsers := dbObj.Members
	var todayUserId uint
	if todayHistory.OnDutyUserId > 0 {
		todayUserId = todayHistory.OnDutyUserId
	} else {
		// 用当前第一个人
		todayUserId = onDutyUsers[0].ID
	}

	// 今天的日期
	today, _ := time.Parse("2006-01-02", time.Now().Format("2006-01-02"))

	// 需要生成未来几天的
	futureNum := int(endDayTime.Sub(today).Hours() / 24)

	// 遍历非 剩下的数组
	// 到这里无外乎 2种逻辑
	// 1. 未来要生成的天数 < 一轮列表
	// 2. 未来要生成的天数 > 一轮列表
	// 一轮列表
	oneShiftPlans := []*models.User{}
	futureUsers := []*models.User{}
	for _, user := range dbObj.Members {
		user := user
		tmp := []*models.User{}
		for i := 0; i < dbObj.ShiftDays; i++ {
			tmp = append(tmp, user)
		}

		oneShiftPlans = append(oneShiftPlans, tmp...)

	}

	// 先是第一轮
	firstLeftUser := []*models.User{}
	todayUserIdIndex := 0
	for index, user := range oneShiftPlans {
		user := user
		if user.ID == todayUserId {
			todayUserIdIndex = index
			break
		}
	}

	// oneShiftPlans=10  todayUserIdIndex=2  futureNum=6
	// firstLeftNum=8

	firstLeftNum := len(oneShiftPlans) - 1 - todayUserIdIndex
	//fmt.Println("firstLeftNum", firstLeftNum)
	//fmt.Println("futureNum", futureNum)
	// 说明第一轮 就够了，还有剩余
	// firstLeftUser 就是未来所有的
	if futureNum <= firstLeftNum {
		addNum := 0
		for index, user := range oneShiftPlans {
			user := user
			if index > todayUserIdIndex && addNum < futureNum {
				futureUsers = append(futureUsers, user)
				addNum++
			}
		}

	} else {
		// 说明第一轮 不够，全部填充
		todayUserIdIndex++
		if todayUserIdIndex == len(oneShiftPlans) {
			todayUserIdIndex--
		}
		//
		firstLeftUser = append(firstLeftUser, oneShiftPlans[todayUserIdIndex:]...)
		futureNum = futureNum - len(firstLeftUser)
		beishu := futureNum / len(oneShiftPlans)
		yushu := futureNum % len(oneShiftPlans)
		if beishu == 0 {
			// 说明要填充的不足一轮，按要填充的算
			yushu = futureNum
		}

		futureUsers = append(futureUsers, firstLeftUser...)
		// 先填充倍数

		for i := 0; i < beishu; i++ {
			futureUsers = append(futureUsers, oneShiftPlans...)
		}
		// 再填充余数
		for i := 0; i < yushu; i++ {
			futureUsers = append(futureUsers, oneShiftPlans[i])
		}

	}

	finalRes := []string{}
	for index, user := range futureUsers {
		index = index + 1
		day := todayTime.Add(time.Duration(index) * time.Hour * 24)
		dayString := day.Format("2006-01-02")
		finalRes = append(finalRes, fmt.Sprintf("日期:%v 值班人:%v", dayString, user.Username))

		tmpRes = append(tmpRes,
			OndutyOne{
				Date: dayString,
				User: user,
			},
		)

	}

	// 最终进行一轮判断： startDay > today还要大
	ffRes := []OndutyOne{}
	// 遍历生成map
	tmp := map[string]string{}
	userNameMap := map[string]string{}
	originUserMap := map[string]string{}
	for _, node := range tmpRes {
		// 先去获取换班记录
		dbChange, _ := models.GetMonitorOndutyChangeByOndutyGroupIdAndDay(dbObj.ID, node.Date)
		if dbChange.OnDutyUserId > 0 {
			user, _ := models.GetUserById(int(dbChange.OnDutyUserId))
			oriUser, _ := models.GetUserById(int(dbChange.OriginUserId))
			if user.RealName != "" {
				node.User = user
				node.OriginUser = oriUser.RealName
			}

		}

		thisDateTime, _ := time.Parse("2006-01-02", node.Date)
		if thisDateTime.Unix() < startDayTime.Unix() {
			continue
		}
		ffRes = append(ffRes, node)
		tmp[node.Date] = node.User.RealName
		originUserMap[node.Date] = node.OriginUser

		userNameMap[node.Date] = node.User.Username
	}

	ondutyPlanResponse := OndutyPlanResponse{}
	ondutyPlanResponse.Details = ffRes
	ondutyPlanResponse.Map = tmp
	ondutyPlanResponse.UserNameMap = userNameMap
	ondutyPlanResponse.OriginUserMap = originUserMap
	sc.Logger.Info("打印结果",
		zap.Any("finalRes", finalRes))
	common.OkWithData(ondutyPlanResponse, c)
}

func deleteMonitorOndutyGroup(c *gin.Context) {
	sc := c.MustGet(common.GIN_CTX_CONFIG_CONFIG).(*config.ServerConfig)
	// 校验一下 menu字段
	id := c.Param("id")
	sc.Logger.Info("删除值班组", zap.Any("id", id))

	// 先 去db中根据id找到这个对象
	intVar, _ := strconv.Atoi(id)
	dbObj, err := models.GetMonitorOndutyGroupById(intVar)
	if err != nil {
		sc.Logger.Error("根据id找值班组错误", zap.Any("值班组", id), zap.Error(err))
		common.FailWithMessage(err.Error(), c)
		return
	}

	//  做个保护，查询一下template 中有没有用到 这个id的
	dbSendGroups, _ := models.GetMonitorSendGroupByOndutyGroupId(uint(intVar))
	if dbSendGroups != nil && len(dbSendGroups) > 0 {
		msg := "根据值班组id找到了发送组-禁止删除值班组"
		sc.Logger.Error(msg, zap.Any("值班组", id), zap.Error(err))
		common.FailWithMessage(msg, c)
		return
	}

	err = dbObj.DeleteOne()
	if err != nil {
		sc.Logger.Error("根据id删除值班组错误", zap.Any("值班组", id), zap.Error(err))
		common.FailWithMessage(err.Error(), c)
		return
	}
	common.OkWithMessage("删除成功", c)
}

func getMonitorOndutyGroupOne(c *gin.Context) {
	sc := c.MustGet(common.GIN_CTX_CONFIG_CONFIG).(*config.ServerConfig)
	// 校验一下 menu字段
	id := c.Param("id")
	sc.Logger.Info("获取值班组", zap.Any("id", id))

	// 先 去db中根据id找到这个对象
	intVar, _ := strconv.Atoi(id)
	dbObj, err := models.GetMonitorOndutyGroupById(intVar)
	if err != nil {
		sc.Logger.Error("根据id找值班组错误", zap.Any("值班组", id), zap.Error(err))
		common.FailWithMessage(err.Error(), c)
		return
	}
	dbObj.FillFrontAllData()

	common.OkWithData(dbObj, c)
}
